﻿//-----------------------------------------------------------------------
// <copyright file="MainPage.xaml.cs" company="Studio Arcade Ltd">
// Copyright © Studio Arcade Ltd 2012.
// All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// This code is made available under the Ms-PL or GPL as appropriate.
// Please see LICENSE.txt for more details
// </copyright>
// <Author>Matt Lacey</Author>
// <Author>Laurie Brown</Author>
//-----------------------------------------------------------------------

namespace NodeGardenSl
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;
    using System.Windows;
    using System.Windows.Media;
    using System.Windows.Shapes;
    using System.Windows.Threading;

    using Microsoft.Phone.Controls;

    using Newtonsoft.Json;
    using NodeGardenLib;
    using NodeGardenSl.ViewModels;
using Microsoft.Expression.Controls;
    using System.IO.IsolatedStorage;

    /// <summary>
    /// The page displaying the node garden
    /// </summary>
    public partial class MainPage : PhoneApplicationPage
    {
        private const int FeedInterval = 1;//2;
        private const int MaxNumberOfPaths = 8;

        /// <summary>
        /// Maximum value for the line width, based on connectedness
        /// </summary>
        private const float StrokeWeightMin = 1.0f;

        /// <summary>
        /// Minimum value for the line width, based on connectedness
        /// </summary>
        private const float StrokeWeightMax = 7.0f;

        /// <summary>
        /// minimum distance between 2 nodes for a line to be drawn between the 2
        /// </summary>
        private const int MinDist = 300;

        /// <summary>
        /// Number of lines created
        /// </summary>
        private const int NumberOfAvailableLines = 150;

        /// <summary>
        /// Timer used to trigger periodic node movement
        /// </summary>
        private readonly DispatcherTimer nodeMovementTimer = new DispatcherTimer();

        /// <summary>
        /// Looks after the garden, detects events and manages communication between gardens/devices
        /// </summary>
        private Gardener gardener;

        /// <summary>
        /// Random number generator
        /// </summary>
        private Random rand = new Random();

        /// <summary>
        /// All the nodes used in the visualization
        /// </summary>
        private List<VisualNode> nodes;

        /// <summary>
        /// All the lines used to connect nodes
        /// </summary>
        private List<ViewModelNodeConnector> nodeConnectors;


        private DispatcherTimer timer;
        private DispatcherTimer feedLoader;

        private string twitterName;
        private TweetFeedWp7.Feed feed;
        /// <summary>
        /// Initializes a new instance of the <see cref="MainPage"/> class.
        /// </summary>
        public MainPage()
        {
            this.InitializeComponent();
            InitialiseFeed();

            IsolatedStorageSettings.ApplicationSettings.TryGetValue<string>("TwitterName", out this.twitterName);
            TwitterName.Text = String.IsNullOrWhiteSpace(this.twitterName) ? string.Empty : this.twitterName;
            
            this.DisplayedVersionNumber.Text = Assembly.GetExecutingAssembly().FullName.Split('=')[1].Split(',')[0];


            //this.BackgroundStream.DataContext = new ViewModel("background");

            this.timer = new DispatcherTimer();
            this.timer.Interval = TimeSpan.FromMilliseconds(900);
            this.timer.Tick += new EventHandler(TimerTick);

            this.feedLoader = new DispatcherTimer();
            this.feedLoader.Interval = TimeSpan.FromSeconds(2);
            this.feedLoader.Tick += FeedLoaderTick;
        }
  
        private void InitialiseFeed()
        {
            this.feed = new TweetFeedWp7.Feed("Hello there, how are you doing today? Welcome to Alphalabs Tweet Garden, we like type in Windows Phone :-)");
            feed.FeedReady += (object feedSender, TweetFeedWp7.FeedReadyArg feedReadyArg) =>
            {
                var nodeWithFeed = this.nodes.FirstOrDefault(n => n.ViewModel.TwitterName.Equals(feedReadyArg.TwitterName));
                feedReadyArg.Messages.ForEach(m => nodeWithFeed.ViewModel.AddMessage(m));

                FakeDataForNonTwitterNodes();

                if (feed.FinishedBatched)
                {
                    feedLoader.Start();
                }
                else
                {
                    feed.NextFeed();
                }
            };
        }


        private bool checkForDemoMode = false;
        void FeedLoaderTick(object sender, EventArgs e)
        {
            feedLoader.Stop();
            
            List<string> nodesWithFeed = this.nodes.Select(n => n.ViewModel.TwitterName).Where(s => !String.IsNullOrWhiteSpace(s)).ToList();
            if (checkForDemoMode)
            {
                checkForDemoMode = false;
                int countDelta = 3 - nodesWithFeed.Count;
                if (countDelta>0)
                {
                    List<string> demoNames = new List<string> { "slodge", "mlacey", "clearbreezed", "keyboardp" };
                    var randNames = demoNames.OrderBy(n => Guid.NewGuid()).ToArray();
                    //for (int i = 0; i < countDelta; i++)
                    {
                        string name = randNames[0];
                        ViewModel newViewModel = new ViewModel(name);
                        var vn = new VisualNode(newViewModel, rand, MainCanvas)
                        {
                            X = RandomX(MainCanvas),
                            Y = RandomY(MainCanvas),
                            Tag = "",
                            Id = Guid.NewGuid().ToString(),
                            DisableVirtualMovement = false
                        };

                        AddNewNodeFromGarden(vn);
                        
                    }
                }
            }
            else
            {
                checkForDemoMode = true;
            }


            //feed.Tweets.CollectionChanged += (collection, args) =>
            //    {
            //        System.Collections.ObjectModel.ObservableCollection<string> tweets = collection as System.Collections.ObjectModel.ObservableCollection<string>;
            //        if (tweets != null)
            //        {
            //            tweets.ToList().ForEach(t => viewModel.AddMessage(t));
            //        }
            //    };

            
            feed.GetFeedsForList(nodesWithFeed);
            feedLoader.Interval = TimeSpan.FromMinutes(FeedInterval);            
        }

        private double RandomY(System.Windows.Controls.Canvas MainCanvas)
        {
            double Y = (double)this.rand.Next(20, (int)MainCanvas.Height);
            return Y;
        }

        private double RandomX(System.Windows.Controls.Canvas MainCanvas)
        {
            double X = (double)this.rand.Next(20, (int)MainCanvas.Width);
            return X;
        }
  
        private void FakeDataForNonTwitterNodes()
        {
            this.nodes.Where(n => String.IsNullOrWhiteSpace(n.ViewModel.TwitterName)).ToList().ForEach(n => n.ViewModel.AddMessage(". "));
        }

        void TimerTick(object sender, EventArgs e)
        {
            if (this.nodeConnectors.Count > 0)
            {
                foreach (var connector in this.nodeConnectors)
                {
                    var viewModel = connector.From.ViewModel;
                    if (viewModel != null)
                    {
                        viewModel.Next();
                    }
                }
            }
            else
            {
                var viewModelNodes = this.nodes.Where(n => n.ViewModel.HasData()).ToList();
                viewModelNodes.ForEach(n => n.ViewModel.Next());
                viewModelNodes.ForEach(n => n.ViewModel.OutputItems.Clear());
            }
            
        }

        /// <summary>
        /// Called when a page becomes the active page in a frame.
        /// </summary>
        /// <param name="e">An object that contains the event data.</param>
        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            if (this.gardener != null)
            {
                this.gardener.Reconnect();
            }
            
            base.OnNavigatedTo(e);
        }

        /// <summary>
        /// Called just before a page is no longer the active page in a frame.
        /// </summary>
        /// <param name="e">An object that contains the event data.</param>
        protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
        {
            if (this.gardener != null)
            {
                this.gardener.Dispose();
            }

            if (this.timer != null)
            {
                this.timer.Stop();
                this.feedLoader.Stop();
            }
        }

        /// <summary>
        /// The start button was clicked.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void StartClicked(object sender, RoutedEventArgs e)
        {
            this.gardener = new Gardener();

            IsolatedStorageSettings.ApplicationSettings["TwitterName"] = TwitterName.Text;
            IsolatedStorageSettings.ApplicationSettings.Save();
            ViewModel viewModel = new ViewModel(TwitterName.Text);
            this.DataContext = viewModel;

            var comm = CommType.Udp;

            if (this.WebComms.IsChecked ?? false)
            {
                comm = CommType.Web;
            }

            // Define the way the gardener should behave
            var settings = new GardenerSettings
            {
                CommType = comm,
                EnableColorDetection = this.EnableColorDetection.IsChecked ?? false,
                EnableImageDetection = this.EnableImageDetection.IsChecked ?? false,
                EnableShakeDetection = this.EnableShakeDetection.IsChecked ?? false,
                EnableNoiseDetection = this.EnableNoiseDetection.IsChecked ?? false,
                NoiseThreshold = 1500,
                NoiseDuration = 2,
                ColorToDetect = Colors.Blue,
                ColorDetectionThreshold = 40,

                // These two properties must be set like this for the camera to be
                // able to be used to do color and image detection
                //ViewFinderBrush = this.viewfinderBrush,
                //ViewFinderBrushTransform = this.viewfinderBrushTransformation,
            };

            this.gardener.Initialize(settings);

            this.AddHandlersForDetectedEvents();

            // Handle notification, from another device, that a node has been added/changed
            InitialiseNodeChangeHandler();

            this.ConfigureOtherOptions();

            this.nodes = new List<VisualNode>();
            this.nodeConnectors = new List<ViewModelNodeConnector>();

            this.CreateOwnNode();

            this.gardener.WhereIsEveryBody();

            
            this.nodeMovementTimer.Interval = TimeSpan.FromMilliseconds(10);
            this.nodeMovementTimer.Tick += this.UpdateDisplay;
            this.nodeMovementTimer.Start();
            this.feedLoader.Start();
            this.timer.Start();
        }
  
        private void InitialiseNodeChangeHandler()
        {
            // Handle notification, from another device, that a node has been added/changed
            this.gardener.OnNodeChanged += nodeId => this.Dispatcher.BeginInvoke(() =>
            {
                var gardenerNode = this.gardener.Nodes.FirstOrDefault(n => n.Id == nodeId && !String.IsNullOrWhiteSpace(n.Tag));

                var visualNode = this.nodes.FirstOrDefault(n => n.Id == nodeId);

                if (gardenerNode != null)
                {
                    if (visualNode == null)
                    {
                        ViewModel newViewModel = ViewModelFromTag(gardenerNode.Tag);
                        if (newViewModel != null)
                        {
                            var vn = new VisualNode(newViewModel, rand, MainCanvas)
                            {
                                X = gardenerNode.X,
                                Y = gardenerNode.Y,
                                Tag = gardenerNode.Tag,
                                Id = gardenerNode.Id,
                                DisableVirtualMovement = true
                            };

                            AddNewNodeFromGarden(vn);
                        }
                    }
                    else
                    {
                        string tag = ApplySerializedTag(gardenerNode);
                        if (!String.IsNullOrWhiteSpace(tag))
                        {
                            var idx = this.nodes.IndexOf(visualNode);
                            double oldX = this.nodes[idx].X;
                            double oldY = this.nodes[idx].Y;
                            this.nodes[idx].X = gardenerNode.X;
                            this.nodes[idx].Y = gardenerNode.Y;
                            System.Diagnostics.Debug.WriteLine("tag:" + gardenerNode.Tag);
                            System.Diagnostics.Debug.WriteLine(string.Format("UPD {0},{1} to {2},{3} - {4}",
                                oldX, oldY, gardenerNode.X, gardenerNode.Y, gardenerNode.LastUpdated));
                            this.nodes[idx].Tag = tag;
                        }
                    }
                }
                else
                {
                    if (visualNode != null)
                    {                        
                        RemoveNodeFromGarden(visualNode);
                    }
                }
            });
        }

        private ViewModel ViewModelFromTag(string tag)
        {
            if (String.IsNullOrWhiteSpace(tag))
            {
                return new ViewModel("wpug");
            }
            ViewModel viewModel = new ViewModel(twitterName:string.Empty);
            try
            {
                TweetNodeDto dto = JsonConvert.DeserializeObject<TweetNodeDto>(tag);
                //TweetNodeAdapter.ApplyDtoTag(viewModel, dto);
                viewModel.TwitterName = dto.TwitterName;
            }
            catch (Exception exception)
            {
                // unexpected tag, ignore it
                System.Diagnostics.Debug.WriteLine("unexpected tag1 " + exception.Message);
                viewModel = null;
            }
            return viewModel;

        }
  
        private string ApplySerializedTag(Node gardenerNode)
        {
            return gardenerNode.Tag;

            //string tag = gardenerNode.Tag;
            //try
            //{
            //    TweetNodeDto dto = JsonConvert.DeserializeObject<TweetNodeDto>(gardenerNode.Tag);
            //    ConnectorTagHasChanged(gardenerNode, dto);
            //}
            //catch (Exception)
            //{
            //    // unexpected tag, ignore it
            //    System.Diagnostics.Debug.WriteLine("unexpected tag2");
            //    tag = null;
            //}
            //return tag;
        }

        
        private void RemoveNodeFromGarden(VisualNode visualNode)
        {
            RemoveConnection(visualNode);
            visualNode.Remove(this.MainCanvas);
            var pathList = visualNode.ListBox();
            if (pathList != null)
            {
                RemovePathList(pathList);
            }
            this.nodes.Remove(visualNode);
        }

        private static void RemovePathList(PathListBox pathList)
        {
            pathList.Opacity = 0.0;
        }

        

        private void AddNewNodeFromGarden(VisualNode vn)
        {
            var match = this.nodes.FirstOrDefault(n => n.ViewModel.TwitterName.Equals(vn.ViewModel.TwitterName));
            if (match != null)
            {
                if (IsDemoNode(match))
                {
                    match.Id = vn.Id;
                }
                return;
            }

            if (NeedToMakeUpNumbersForDemo())
            {
                var demoNode = this.nodes.FirstOrDefault(n => n.DisableVirtualMovement);
                if (demoNode != null)
                {
                    RemoveNodeFromGarden(demoNode);
                }
            }

            AddNewConnection(vn);
            this.nodes.Add(vn);
            string twitterName = vn.ViewModel.TwitterName;
            if (!String.IsNullOrWhiteSpace(twitterName))
            {
                this.feed.GetFeedsForList(new List<string> { twitterName  });
            }
        }

        private bool NeedToMakeUpNumbersForDemo()
        {
            return this.nodes.Count > 3;
        }

        private static bool IsDemoNode(VisualNode match)
        {
            return match.DisableVirtualMovement;
        }

        private void RemoveConnection(VisualNode visualNode)
        {
            var connectionsToRemove = this.nodeConnectors.Where(c=>c.From.Id.Equals(visualNode.Id) || c.To.Id.Equals(visualNode.Id)).ToList();
            foreach (var connection in connectionsToRemove)
            {
                var connectingLine = connection.ConnectingLine;
                var pathList = connection.ListBox;
                this.nodeConnectors.Remove(connection);
            
                RemoveShapeInvolvedInAnimation(connectingLine);
                RemovePathList(pathList);
                
            }
        }

        private static void RemoveShapeInvolvedInAnimation(Shape connectingLine)
        {
            connectingLine.Opacity = 0.0;
        }

        private ViewModelNodeConnector AddNewConnection(VisualNode vn)
        {
            ViewModelNodeConnector connector = new ViewModelNodeConnector();
            var selfNode = this.nodes.First(n => n.Id.Equals(gardener.GetSelfNodeId()));

            AddFromConnection(vn, selfNode, useArc:true);
            AddFromConnection(selfNode,vn, useArc:false);
            return connector;
        }

        private void AddFromConnection(VisualNode toNode, VisualNode fromNode, bool useArc)
        {
            //<Path Stroke="Black" StrokeThickness="1"  
            //      Data="M 10,100 A 100,50 45 1 0 200,100" />
            
            Path pathForConnector = new Path();
            Line connectingLine = new Line();
            Shape connectingShape;
            if (useArc)
            {
                PathGeometry arcGeometry = new PathGeometry();
                bool useLargeArc = this.rand.Next(1, 100) > 50;
                arcGeometry.Figures.Add(new PathFigure
                {
                    StartPoint = new Point(fromNode.X, fromNode.Y),
                    Segments = new PathSegmentCollection {
                    new ArcSegment { IsLargeArc = useLargeArc, 
                                        Size = new Size(100,50), 
                                        RotationAngle=45.0, 
                                        SweepDirection = System.Windows.Media.SweepDirection.Counterclockwise,
                                        Point = new Point(toNode.X, toNode.Y)}
                }
                });
                pathForConnector.Data = arcGeometry;
                pathForConnector.Stroke = new SolidColorBrush(Colors.LightGray);
                pathForConnector.StrokeThickness = 2.5f;
                pathForConnector.Opacity = 0.0;
                connectingShape = pathForConnector;
            }
            else
            {
                connectingLine.StrokeThickness = 2.5f;
                connectingLine.Stroke = new SolidColorBrush(Colors.LightGray);
                connectingLine.X1 = fromNode.X;
                connectingLine.Y1 = fromNode.Y;
                connectingLine.X2 = toNode.X;
                connectingLine.Y2 = toNode.Y;
                connectingLine.Opacity = 0.0;
                connectingShape = connectingLine;
            };

            MainCanvas.Children.Add(connectingShape);
            ViewModelNodeConnector connection = new ViewModelNodeConnector
            {
                From = fromNode,
                To = toNode,
                ConnectingLine = connectingShape,
                ListBox = MyVisualNode.AttachPathListBoxToShape(fromNode.ViewModel, fromNode.ViewModel.OutputItems, connectingShape, "DataTemplateMessages", "ItemsPanelTemplateMessages")
            };
            connection.ListBox.Visibility = System.Windows.Visibility.Visible;
            this.nodeConnectors.Add(connection);
            MainCanvas.Children.Add(connection.ListBox);
        }

     
        /// <summary>
        /// Updates the display.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void UpdateDisplay(object sender, EventArgs e)
        {
     
           ViewModel viewModel = DataContext as ViewModel;

            for (int i = 0; i < this.nodes.Count; ++i)
            {
                Node nodeI = this.nodes[i];
                MyVisualNode myNode = nodeI as MyVisualNode;
                // update the node's position
                this.nodes[i].Update(MainCanvas.RenderSize);

                for (int j = 0; j < this.nodes.Count; ++j)
                {
                    Node nodeJ = this.nodes[j];   
                    if (i != j)
                    {            
                        // calculate the distance between each 2 nodes
                        float distance =
                            (float)Math.Sqrt(
                                Math.Pow(this.nodes[j].CurrentY - this.nodes[i].CurrentY, 2)
                                + Math.Pow(this.nodes[j].CurrentX - this.nodes[i].CurrentX, 2));


                        ViewModelNodeConnector connector = GetConnectionBetween(nodeI, nodeJ);
                            if (connector != null)
                            {

                                Line line = connector.ConnectingLine as Line;
                                if (line != null)
                                {
                                    line.X1 = nodeI.X;
                                    line.Y1 = nodeI.Y;
                                    line.X2 = nodeJ.X;
                                    line.Y2 = nodeJ.Y;
                                }
                                else
                                {
                                    Path path = connector.ConnectingLine as Path;
                                    if (path != null)
                                    {
                                        PathGeometry g = path.Data as PathGeometry;
                                        var pf = g.Figures[0] as PathFigure;
                                        pf.StartPoint = new Point(nodeI.X, nodeI.Y);
                                        var arc = pf.Segments[0] as ArcSegment;
                                        arc.Point = new Point(nodeJ.X, nodeJ.Y);
                                    }
                                }
                                
                            }
                    }
                }

                // draw the node using the Connectedness
                this.nodes[i].Draw();
                var gardenNode = this.gardener.Nodes.FirstOrDefault(n => n.Id == this.nodes[i].Id);
                if (gardenNode != null)
                {
                    gardenNode.Tag = this.nodes[i].Tag;
                }
                //viewModel.UpdateData();
            }

            if (this.timer != null)
            {
                if (!this.timer.IsEnabled)
                {
                    this.timer.Start();
                    this.feedLoader.Start();
                }
            }
        }

        private ViewModelNodeConnector GetConnectionBetween(Node nodeI, Node nodeJ)
        {
           
            ViewModelNodeConnector connector = this.nodeConnectors.FirstOrDefault(c => c.From.Id.Equals(nodeI.Id) && c.To.Id.Equals(nodeJ.Id));
            return connector;
        }

        /// <summary>
        /// Configures the other options defined before starting the app.
        /// </summary>
        private void ConfigureOtherOptions()
        {
            // This is commented as it relates to a UIElement that is commented out on the page
            ////if (this.ShowVisualDebugger.IsChecked ?? false)
            ////{
            ////    this.CameraView.Opacity = 1;
            ////}

            this.DebugConfigOptions.Visibility = Visibility.Collapsed;
        }

        /// <summary>
        /// Adds the handlers for detected events.
        /// </summary>
        private void AddHandlersForDetectedEvents()
        {
            this.gardener.OnShakeDetected += () => this.Dispatcher.BeginInvoke(
                () =>
                {
                    // This is where you could do something in response to a shake being detected
                });

            this.gardener.OnNoiseDetected += () => this.Dispatcher.BeginInvoke(
                () =>
                {
                    // This is where you could do something in response to a noise being detected
                });

            this.gardener.OnColorDetected += () => this.Dispatcher.BeginInvoke(
                () =>
                {
                    // This is where you could do something in response to a colour being detected with the camera
                });

            this.gardener.OnImageDetected += img => this.Dispatcher.BeginInvoke(
                () =>
                {
                    // This is where you could do something in response to an image being detected
                });
        }

        /// <summary>
        /// Creates the node that can primarily be interacted with and will be reflected on other devices.
        /// </summary>
        private void CreateOwnNode()
        {                        
            var myNode = new MyVisualNode((ViewModel)this.DataContext, this.rand, MainCanvas, this.gardener);

            this.gardener.AddSelfNode(myNode.X, myNode.Y);

            myNode.Id = this.gardener.GetSelfNodeId();

            this.nodes.Add(myNode);
        }
    }
}